/**
*
Java Diagram Package; An extremely flexible and fast multipurpose diagram
component for Swing.
Copyright (C) 2001 Eric Crahen <crahen@cse.buffalo.edu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package diagram.tool;
import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.EventObject;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
import diagram.Diagram;
import diagram.DiagramUI;
import diagram.Figure;
import diagram.FigureEditor;
import diagram.Link;
import diagram.SelectionModel;
/**
* @class EditingTool
*
* @date 08-20-2001
* @author Eric Crahen
* @version 1.0
*
* Add figure editing support to a Diagram. The method used to supprot figure
* editing is a simplified version of the method used in Suns JTable
* implementation.
*
* Editing is triggered whenever a mouse is clicked inside a Figure in a
* Diagram. The FigureEditor for the diagram is obtained and an Component is
* produced to perform the editing. Editing will continue until that Component
* loses focus, either from clicking off or clicking onto another Figure.
*
* Three Action objects are installed in the Diagram ActionMap,
*
* "startEditing"
* "stopEditing"
* "cancleEditing"
*
* A diagram can insert mapping for these actions into its InputMap inorder
* to take advantage of them. Editing can be controlled programtically, on the
* selected Figure, in this way.
*/
public class EditingTool extends AbstractTool {
// Event handlers
protected MouseHandler mouseHandler = new MouseHandler();
protected FocusHandler focusHandler = new FocusHandler();
// Editing actions
protected Action actionStart = new StartEditingAction();
protected Action actionStop = new StopEditingAction();
protected Action actionCancel = new CancelEditingAction();
// Current Diagram
private Diagram diagram;
// Current Figure
private Figure figure;
// Component recieving mouse events during editing. May not be editorComponent.
private Component dispatchComponent;
// Component being used to render the editing
private Component editorComponent;
// Bounds for the editing component (cached)
private Rectangle editorBounds = new Rectangle();
// Current editor
private FigureEditor editor;
// Selected figures
private Figure[] selected = new Figure[1];
/**
* Install the support in the specified Diagram
*
* @param Diagram
*/
public void install(Diagram diagram) {
diagram.addMouseListener(mouseHandler);
diagram.addMouseMotionListener(mouseHandler);
diagram.addFocusListener(focusHandler);
ActionMap map = diagram.getActionMap();
map.put("startEditing", actionStart);
map.put("stopEditing", actionStop);
map.put("cancelEditing", actionCancel);
}
/**
* Uninstall the support from the specified Diagram
*
* @param Diagram
*/
public void uninstall(Diagram diagram) {
diagram.removeMouseListener(mouseHandler);
diagram.removeMouseMotionListener(mouseHandler);
diagram.removeFocusListener(focusHandler);
ActionMap map = diagram.getActionMap();
map.remove("startEditing");
map.remove("stopEditing");
map.remove("cancelEditing");
}
/**
* Check if this support object is currently editing a diagram.
*
* @return boolean
*/
protected boolean isEditing() {
return (editor != null);
}
/**
* @class MouseHandler
*
* Handle mouse actions that have an affect on editing.
*/
protected class MouseHandler extends MouseInputAdapter {
// Cached link array for checking link editor bounds
private Link[] links = new Link[4];
private final boolean shouldIgnore(MouseEvent e) {
return !(!e.isConsumed() && SwingUtilities.isLeftMouseButton(e));
}
/**
* Locate the correct component to recieve mouse events. This should be
* the deepest (topmost) component beneath the MouseEvent.
*
* @param MouseEvent
*/
private final void setDispatchComponent(MouseEvent e) {
Point p = SwingUtilities.convertPoint(diagram, e.getPoint(), editorComponent);
dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent, p.x, p.y);
}
/**
* Translate an event object and dispatch it to the current dispatch
* component. In some cases this may not be the editing component but a
* child within that component.
*
* @return boolean success
*/
private final boolean repostEvent(MouseEvent e) {
if(dispatchComponent == null)
return false;
MouseEvent e2 = SwingUtilities.convertMouseEvent(diagram, e, dispatchComponent);
dispatchComponent.dispatchEvent(e2);
return true;
}
/**
* Check the location of the MouseEvent, if it falls on an editable Figure then
* start editing. Otherwise, request the focus for the table.
*
* @param MouseEvent e
*/
public void mousePressed(MouseEvent e) {
// If no editing is going on and this event should be ignored skip looking for
// figure beneath the mouse since it won't be used
if(!isEditing() && shouldIgnore(e))
return;
// Check the figure beneath the mouse
Diagram diagram = (Diagram)e.getSource();
Figure pressedFigure = findPressedFigure(diagram, e.getPoint());
// If the press is outside the current figure, stop editing
// the current figure
if(isEditing() && pressedFigure != figure)
stopEditing(diagram, figure);
// If editing is progress, or is started by this event then
// forward it to the dispatch component
if(isEditing() || startEditing(diagram, pressedFigure, e)) {
setDispatchComponent(e);
repostEvent(e);
e.consume();
}
}
/**
* Translate the event and release the current dispatch component.
*
* @param MouseEvent
*/
public void mouseReleased(MouseEvent e) {
if(!isEditing() || shouldIgnore(e))
return;
repostEvent(e);
dispatchComponent = null;
}
/**
* Translate the event.
*
* @param MouseEvent
*/
public void mouseDragged(MouseEvent e) {
if(!isEditing() || shouldIgnore(e))
return;
repostEvent(e);
}
/**
* Search for a Figure in the diagram at the given point that can be edited
*/
protected Figure findPressedFigure(Diagram diagram, Point2D pt) {
Figure pressedFigure = diagram.findFigure(pt);
// If no figure was found by direct clicking, check link editor bounds
// for label clicks
if(pressedFigure == null) {
// Request all links
links = (Link[])diagram.getModel().toArray(links);
for(int i=0; i < links.length && links[i] != null; i++) {
if(diagram.getModel().getValue(links[i]) == null)
continue;
// Check editor bounds
FigureEditor editor = diagram.getFigureEditor(links[i].getClass());
editorBounds = (Rectangle) editor.getDecoratedBounds(diagram, links[i], editorBounds);
if(editorBounds.contains(pt))
return links[i];
}
}
return pressedFigure;
}
} /* MouseHandler */
/**
* @class FocusHandler
*
* Repaints the area where the Editing component is displayed whenever the focus
* on the containg diagram moves.
*/
protected class FocusHandler extends FocusAdapter {
public void focusGained(FocusEvent e) {
repaintEditorCell((Diagram)e.getSource());
}
public void focusLost(FocusEvent e) {
repaintEditorCell((Diagram)e.getSource());
}
private final void repaintEditorCell(Diagram diagram) {
diagram.paintImmediately(editorBounds);
}
} /* FocusHandler */
/**
* Try to start editing a Figure
*
* @param Diagram
* @param Figure
* @param EventObject
*
* @return boolean true if editing was successfully started.
*/
protected boolean startEditing(Diagram diagram, Figure figure, EventObject e) {
if(figure != null && diagram != null) {
// Get the editor
editor = diagram.getFigureEditor(figure.getClass());
if(editor == null || !editor.isCellEditable(e)) {
editor = null;
return false;
}
// Select the cell being edited
SelectionModel selectionModel = diagram.getSelectionModel();
if(editor.shouldSelectCell(e))
selectionModel.add(figure, true);
// Get the component for editing
boolean isSelected = selectionModel.contains(figure);
editorComponent = editor.getFigureEditorComponent(diagram, figure, isSelected);
if(editorComponent == null)
throw new RuntimeException("Bad FigureEditor!");
fireToolStarted();
// Configure the editing component
editorBounds = (Rectangle)editor.getDecoratedBounds(diagram, figure, editorBounds);
editorComponent.setBounds(editorBounds.x, editorBounds.y,
editorBounds.width, editorBounds.height);
diagram.add(editorComponent);
editorComponent.validate();
editorComponent.setVisible(true);
editorComponent.requestFocus();
this.diagram = diagram;
this.figure = figure;
return true;
}
return false;
}
/**
* Stop editing a certain figure
*
*/
protected void stopEditing(Diagram diagram, Figure figure) {
diagram.getModel().setValue(figure, editor.getCellEditorValue());
editor.stopCellEditing();
removeEditor();
fireToolFinished();
}
/**
* Remove the editing component
*
* @post Clears any reference to the editing components no longer
* in use.
*/
protected void removeEditor() {
editorBounds = (Rectangle)editorComponent.getBounds(editorBounds);
diagram.remove(editorComponent);
// Update the area where the editor was
diagram.requestFocus();
DiagramUI ui = (DiagramUI)diagram.getUI();
ui.damageFigure(figure);
ui.repaintRegion(editorBounds);
// Discard the unused references
diagram = null;
editor = null;
figure = null;
editorComponent = null;
}
/**
* Action to start editing, and pass focus to the editor. If a single
* Figure is selected, editing will begin on that figure.
*/
private class StartEditingAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
Diagram diagram = (Diagram)e.getSource();
SelectionModel model = diagram.getSelectionModel();
if(model.size() != 1)
return;
selected = (Figure[])model.toArray(selected);
startEditing(diagram, selected[0], e);
}
}
/**
* Action to stop editing, and pass focus to the diagram.
*/
private class StopEditingAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
if(!isEditing())
return;
editor.stopCellEditing();
removeEditor();
}
}
/**
* Action to cancel editing, and pass focus to the diagram.
*/
private class CancelEditingAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
if(!isEditing())
return;
editor.cancelCellEditing();
removeEditor();
}
}
}